//-------------------------------------------------------------------
//	Copyright (c) 2013  Zhirnov Andrey
//	This file is part of the "Noise Library" project.
//	See copyright notice in "linNoiseCL.h".
//-------------------------------------------------------------------

#include "CProgramNative.h"
#include "../Context/CContext.h"

namespace Noise
{

	//
	// Pixel Coord Transformation
	//

	struct TPixelCoordTransform
	{
		uvec3	_size;
		real3	_scale;

		TPixelCoordTransform (const uvec3 &size, const real3 &scale) : _size(size), _scale(scale) {}

		template <typename Real>
		TVec<Real,3> Transform (const int3 &coord) const
		{
			typedef TVec<Real,3>	Real3;
			return coord.To<Real3>() / _size.To<Real3>() * _scale.To<Real3>();
		}
	};


	//
	// Color Converter
	//

	struct TColorConverter
	{
		template <typename Dst, typename Real>
		static Dst Convert (const TVec<Real,4> &color)
		{
			return color.To<Dst>();
		}
	};



	// constructor
	CProgramNative::CProgramNative ()
	{
	}

	// destructor
	CProgramNative::~CProgramNative ()
	{
	}

	// Delete
	void CProgramNative::Delete ()
	{
		CProgram::Delete();

		_format = EPixelFormat(0);
	}
	
	// _CreateDefaultOutput
	void CProgramNative::_CreateDefaultOutput (CSourcePtr source, CSourcePtr &outSource)
	{
		int src_type = 0;

		source->GetInteger( SOURCE_TYPE, &src_type, 1 );

		if ( src_type != RENDER_OUTPUT )
		{
			outSource = CONTEXT()->CreateSource( RENDER_OUTPUT );
			outSource->SetArg( SOURCE_0, source );
		}
		else
			outSource = source;

		_format = outSource.To<ISourceOutputPtr>()->GetFormat();
	}

	// Build
	EResult CProgramNative::Build (CSourcePtr source, EBuildOptions options)
	{
		_srcData.Clear();

		CSourcePtr	out_src;

		_CreateDefaultOutput( source, out_src );

		_options = options;
		_source = out_src;
		return OK;
	}

	// GetInteger
	EResult CProgramNative::GetInteger (EProgramQuery pname, int *v, int count) const
	{
		CHECK_ARGUMENT_R( count >= 1, INVALID_VALUE );

		enum { MAX_IMAGE_SIZE = 1 << 14 };

		switch ( pname )
		{
			case PROGRAM_BUILD_STATE :
				v[0] = _source.IsNotNull();
				return OK;

			case PROGRAM_TYPE :
				v[0] = OPENCL_PROGRAM;
				return OK;

			case PROGRAM_OUT_FORMAT :
				v[0] = _format;
				return OK;

			case PROGRAM_BUILD_LOG_LENGTH :
				v[0] = _srcData.buildLog.Size();
				return OK;

			case PROGRAM_BUILD_OPTIONS :
				v[0] = _options;
				return OK;

			case PROGRAM_MAX_IMAGE_SIZE :
				for (uint i = 0; i < Clamp<uint>( count, 1, 3 ); ++i) {
					v[i] = MAX_IMAGE_SIZE;
				}
				return OK;

			case PROGRAM_NUM_IMAGE_FORMATS :
				v[0] = GetSupportedFormats().Count();
				return OK;

			case PROGRAM_IMAGE_FORMATS :
				CHECK_ARGUMENT_R( (uint)count >= GetSupportedFormats().Count(), INVALID_VALUE );
				MemCopy( v, GetSupportedFormats().ptr(), GetSupportedFormats().Size() );
				return OK;
		}
		
		return CProgram::GetInteger( pname, v, count );
	}
	
#ifdef _NOISE_NO_NATIVE
	
	EResult CProgramNative::RenderToMemory (const uvec3 &dim, const uvec2 &sizes, EPixelFormat format, EPixelType type, void *pixels)
	{
		return INVALID_OPERATION;
	}

#else
	// RenderToMem
	template <	typename Dst,
				typename Transform,
				typename Real,
				TVec<Real,4> (ISourceOutput::*GetColor)(const TVec<Real,3> &)
			 >
	void RenderToMem (const uvec3 &dim, const uvec2 &sizes, uint8 *pixels, ISourceOutput *src, Transform tr)
	{
		typedef TColorConverter		ColorConv;

		usize offset = 0;

		for (uint z = 0; z < dim.z; ++z)
		{
			for (uint y = 0; y < dim.y; ++y)
			{
				offset = sizes[1] * z + sizes[0] * y;

				for (uint x = 0; x < dim.x; ++x)
				{
					*(Dst *)(pixels + offset) = ColorConv::Convert<Dst>( (src->*GetColor)( tr.Transform<Real>( int3(x,y,z) ) ) );
					offset += sizeof(Dst);
				}
			}
		}
	}

	// RenderToMemory
	EResult CProgramNative::RenderToMemory (const uvec3 &dim, const uvec2 &sizes, EPixelFormat format, EPixelType type, void *pixels)
	{
		typedef void (* PRenderProc_t)(const uvec3 &, const uvec2 &, uint8 *, ISourceOutput *, TPixelCoordTransform);

		static const PRenderProc_t	s_aRenderProcs32[] = {
			& RenderToMem< fvec1,  TPixelCoordTransform, float32, &ISourceOutput::NativeFunction4_32 >,
			& RenderToMem< fvec2,  TPixelCoordTransform, float32, &ISourceOutput::NativeFunction4_32 >,
			& RenderToMem< fvec3,  TPixelCoordTransform, float32, &ISourceOutput::NativeFunction4_32 >,
			& RenderToMem< fvec4,  TPixelCoordTransform, float32, &ISourceOutput::NativeFunction4_32 >,
			& RenderToMem< dvec1,  TPixelCoordTransform, float32, &ISourceOutput::NativeFunction4_32 >,
			& RenderToMem< dvec2,  TPixelCoordTransform, float32, &ISourceOutput::NativeFunction4_32 >,
			& RenderToMem< dvec3,  TPixelCoordTransform, float32, &ISourceOutput::NativeFunction4_32 >,
			& RenderToMem< dvec4,  TPixelCoordTransform, float32, &ISourceOutput::NativeFunction4_32 >
		};
		
		static const PRenderProc_t	s_aRenderProcs64[] = {
			& RenderToMem< fvec1,  TPixelCoordTransform, float64, &ISourceOutput::NativeFunction4_64 >,
			& RenderToMem< fvec2,  TPixelCoordTransform, float64, &ISourceOutput::NativeFunction4_64 >,
			& RenderToMem< fvec3,  TPixelCoordTransform, float64, &ISourceOutput::NativeFunction4_64 >,
			& RenderToMem< fvec4,  TPixelCoordTransform, float64, &ISourceOutput::NativeFunction4_64 >,
			& RenderToMem< dvec1,  TPixelCoordTransform, float64, &ISourceOutput::NativeFunction4_64 >,
			& RenderToMem< dvec2,  TPixelCoordTransform, float64, &ISourceOutput::NativeFunction4_64 >,
			& RenderToMem< dvec3,  TPixelCoordTransform, float64, &ISourceOutput::NativeFunction4_64 >,
			& RenderToMem< dvec4,  TPixelCoordTransform, float64, &ISourceOutput::NativeFunction4_64 >
		};

		STATIC_ASSERTE( sizeof(s_aRenderProcs32) == sizeof(s_aRenderProcs64) );


		CHECK_ARGUMENT_R( pixels != null, INVALID_VALUE );

		uint proc_id = -1;

		switch (type)
		{
			case FLOAT32 :	proc_id = 0;	break;
			case FLOAT64 :	proc_id = 4;	break;
		}

		switch (format)
		{
			case R :		break;
			case RG :		proc_id += 1;	break;
			case RGB :		proc_id += 2;	break;
			case RGBA :		proc_id += 3;	break;
		}

		const uint bpp = _GetBytePerPixel( format, type );

		CHECK_VALUE_R( bpp != 0, INVALID_ENUM );
		CHECK_VALUE_R( proc_id >= 0 && proc_id < count_of(s_aRenderProcs32), INVALID_ENUM );

		const uvec2 min_sizes = uvec2( dim.x, dim.x * dim.y ) * bpp;

		if ( EnumCmp( _options, BUILD_FLOAT64 ) )
		{
			s_aRenderProcs64[proc_id]( dim, Max( sizes, min_sizes ), (uint8 *)pixels,
										_source.ptr(), TPixelCoordTransform( dim, vec3(1.0f) ) );
		}
		else
		{
			s_aRenderProcs32[proc_id]( dim, Max( sizes, min_sizes ), (uint8 *)pixels,
										_source.ptr(), TPixelCoordTransform( dim, vec3(1.0f) ) );
		}
		return OK;
	}
#endif	// _NOISE_NO_NATIVE

	// RenderToImage
	EResult CProgramNative::RenderToImageCL (cl_mem imageId)
	{
		return INVALID_OPERATION;
	}
	
	// RenderToSubImageCL
	EResult CProgramNative::RenderToSubImageCL (cl_mem imageId, const svec3 &offset, const svec3 &dim)
	{
		return INVALID_OPERATION;
	}

	// RenderToTextureGL
	EResult CProgramNative::RenderToTextureGL (GLenum target, GLuint id, int level)
	{
		return INVALID_OPERATION;
	}

	// RenderToTextureDX
	EResult CProgramNative::RenderToTextureDX (void *dxTex)
	{
		return INVALID_OPERATION;
	}

	// CreateCLProgram
	bool CProgramNative::CreateProgram (CProgramPtr &prog, CContext *context, EProgram type)
	{
		ASSERT( type == NATIVE_PROGRAM );

		prog = new CProgramNative();
		return true;
	}

	// GetSupportedFormats
	const uni_array<const CProgram::Format> CProgramNative::GetSupportedFormats ()
	{
		static const Format	_formats[] = {
			{ R,	FLOAT32 },	{ RG,	FLOAT32 },	{ RGB,	FLOAT32 },	{ RGBA,	FLOAT32 },
			{ R,	FLOAT64 },	{ RG,	FLOAT64 },	{ RGB,	FLOAT64 },	{ RGBA,	FLOAT64 },
		};

		return uni_array<const Format>( _formats );
	}


}	// Noise